import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';

import 'package:im_app/im/dao/ImFileDao.dart';
import 'package:im_app/im/util/ImSingleton.dart';
import 'package:logger/logger.dart';

import '../dao/ImGroupDao.dart';
import '../dao/ImGroupUserDao.dart';
import '../dao/ImMessageDao.dart';
import '../dao/ImMessageSendDao.dart';
import '../dao/ImSessionDao.dart';
import '../dao/ImUserDao.dart';
import '../pojo/ImPojo.dart';
import '../socket/dto/business/Message.dart';
import '../socket/dto/business/PullData2.dart';
import '../util/DateUtil.dart';
import '../util/FileUtil.dart';
import '../util/ImCacheFactory.dart';
import '../util/ImageUtil.dart';
import '../util/MessageUtil.dart';
import 'BaseService.dart';
import 'ImGroupService.dart';

class ImMessageService extends BaseService {

	final _logger = Logger(printer: PrettyPrinter(),);

	// final _groupDao = ImGroupDao();
	// final _groupUserDao = ImGroupUserDao();
	// final _sessionDao = ImSessionDao();
	// final _messageDao = ImMessageDao();
	// final _messageSendDao = ImMessageSendDao();

	/// 1, 如果消息的会话为空，就创建消息会话
	/// 2, 设置会话最新消息
	/// 3, 消息保存或更新，删除
	/// 4, 界面展示消息（引用消息不展示）
	Future<List<ImMessage>> saveOrUpdates(String userId, List<MessageObject> messageObjectList, bool isSynMessage) async {
		if (messageObjectList.isEmpty) return [];

		Set<ImMessage> insertImMessageSet =  LinkedHashSet();
		Set<ImMessage> updateMessageSet =  LinkedHashSet();
		Set<ImMessage> deleteMessageSet =  LinkedHashSet();
		Set<ImSession> updateSessionSet =  LinkedHashSet();

		final sessionDao = ImSessionDao();
		final messageDao = ImMessageDao();
		final messageSendDao = ImMessageSendDao();

		for (MessageObject messageObject in messageObjectList) {
			ImMessage? imMessage = null;
			String content = messageObject.content;
			if (messageObject is! RefMessageObject) { // 非引用消息
				ImSession? imSession = await _getOrBuildImSession(userId, messageObject);
				if (imSession == null) {
					imSession = await _buildImSession(userId, messageObject);
					sessionDao.insert(imSession);
				} else {
					String latelyMessage = MessageUtil.buildLastedMessage(ImMessageType.ofName(messageObject.messageType.name), content);
					imSession.latelyMessage = latelyMessage;

					imSession.updateTime = DateTime.now();
					updateSessionSet.add(imSession);
				}
				imMessage = await _toImMessage(userId, imSession!, messageObject);
			}  else { // 引用消息没有会话Id
				imMessage = await _toImMessage(userId, null, messageObject);
			}

			if (imMessage == null) {
				continue;
			}
			ImMessage? dbMessage = await messageDao.getByServerIdAndUserId(imMessage.serverId!, userId);
			if (dbMessage != null) {
				if (imMessage.status == ImMessageStatus.withdraw || imMessage.status == ImMessageStatus.updated) {
					updateMessageSet.add(imMessage);
				} else if (imMessage.status == ImMessageStatus.delete) {
					deleteMessageSet.add(imMessage);
				} else if (imMessage.status == ImMessageStatus.ref) {
					continue;
				} else {
					_logger.w("客户端不应该收到这个消息，messageId: ${imMessage.serverId}, currentUserId: ${userId}, status: ${imMessage
							.status}");
				}
			} else {
				insertImMessageSet.add(imMessage);
			}
		}

		var insertImMessages = insertImMessageSet.toList();
		await messageDao.batchInserts(insertImMessages);
		await messageSendDao.batchInserts2(insertImMessages);

		var updateMessages = updateMessageSet.toList();
		await messageDao.batchUpdates(userId, updateMessages);
		await messageDao.batchDeletes(deleteMessageSet.toList());

		await sessionDao.batchUpdates(userId, updateSessionSet.toList());

		await initRefMessages(insertImMessages);

		if (isSynMessage) {
			// 计算未读消息数
			Map<int, List<ImMessage>> sessionMessages = Map.fromIterable(insertImMessages, key: (m)=>m.sessionId,
					value: (m) =>insertImMessages.where((item)=>m.serverId == item.serverId).toList());

			sessionMessages.forEach((key, value){
				var hotMessageTotal = value.where((m)=>!m.isSender(userId))
					.where((m)=>m.actionStatus == ImMessageActionStatus.none).toList().length;
				if (hotMessageTotal > 0) {
					sessionDao.incHotMessageNum(key, hotMessageTotal);
					ImCacheFactory.getInstance().incHotMessage(key, hotMessageTotal);
				}
			});
		}

		return insertImMessages.toList();
	}

	Future<List<ImMessage>> getNewsBySessionId(int sessionId, DateTime? serverReceiveDate, int size) async {
		final messageDao = ImMessageDao();
		final groupDao = ImGroupDao();

		List<ImMessage> messages = await messageDao.getNewsBySessionId(sessionId, serverReceiveDate, size);
		ImGroup? group = null;
		for (var message in messages) {
			message.fromImUser = await ImCacheFactory.getInstance().getImUser(message.fromUserId!);
			if (message.isGroup) {
				group = group ?? await groupDao.getByCodeAndUserId(message.groupCode!, message!.currentUserId!);
				var fromGroupUser = group!.groupUserList!.firstWhere((gu)=>gu.userId == message.fromUserId);
				if (fromGroupUser.userName == null) {
					fromGroupUser.userName = message.fromImUser!.userName;
				}
				message.fromGroupUser = fromGroupUser;
			}
		}
		return messages;
	}

	Future<List<ImMessage>> getOldBySessionId(int sessionId, DateTime serverReceiveDate, int size) async {
		final messageDao = ImMessageDao();
		final groupDao = ImGroupDao();

		List<ImMessage> messages = await messageDao.getOldBySessionId(sessionId, serverReceiveDate, size);
		ImGroup? group = null;
		for (var message in messages) {
			message.fromImUser = await ImCacheFactory.getInstance().getImUser(message.fromUserId!);

			group = group ?? await groupDao.getByCodeAndUserId(message.groupCode!, message!.currentUserId!);
			var fromGroupUser = group!.groupUserList!.firstWhere((gu)=>gu.userId == message.fromUserId);
			if (fromGroupUser.userName == null) {
				fromGroupUser.userName = message.fromImUser!.userName;
			}
			message.fromGroupUser = fromGroupUser;
		}
		return messages;
	}

	Future<List<List<DateTime>>> getDaysForMonth(int sessionId, DateTime startDate, int size) async {
		final messageDao = ImMessageDao();
		final messageSendDao = ImMessageSendDao();

		List<String> days = await messageDao.getDays(sessionId, startDate, size);
		List<DateTime> dates = days.map((d)=>DateTime.parse(d)).toList();
		List<List<DateTime>> result = [];
		DateTime? month = null;
		List<DateTime> months = [];
		for (var day in dates) {
			if (month == null) {
				month = day;
				months.add(day);
				if (months.length == 1) {
					result.add(months);
				}
			} else {
				if (DateUtil.isSameMonth(day, month)) {
					months.add(day);
					if (months.length == 1) {
						result.add(months);
					}
				} else {
					months = [];
					month = day;
					months.add(day);
					if (months.length == 1) {
						result.add(months);
					}
				}
			}
		}
		return result;
	}

	Future<void> cleanMessage(String userId, int sessionId) async {
		final messageDao = ImMessageDao();
		final messageSendDao = ImMessageSendDao();

		int pageSize = 1000;
		List<ImMessage> messages = await messageDao.getNewsBySessionId(sessionId, null, pageSize);

		// 先删除附件
		for (ImMessage message in messages) {
			if (!message.messageType.isFile()) continue;
			if (message.messageExt == null) {
				message.initMessageExt();
			}
			
			if (message.messageExt?.remote??false) {
				deleteFile(MessageUtil.getFilePath2(message));
			}
		}

		List<int> messageIds = messages.map((m)=>m.id!).toList();
		messageDao.cleanMessages(userId, messageIds);
		messageSendDao.cleanMessageSends(messageIds);
		if (messages.length == pageSize) {
			cleanMessage(userId, sessionId);
		}
	}

	Future<ImMessage> _toImMessage(String userId, ImSession? imSession, MessageObject messageObject) async {
		var messageType = ImMessageType.ofName(messageObject.messageType.name);
		var imMessage = ImMessage(fromUserId: messageObject.fromUserId, messageType: messageType, content: messageObject.content);
		imMessage.fromImUser = await ImCacheFactory.getInstance().getImUser(messageObject.fromUserId);
		imMessage.currentUserId = userId;
		if (messageObject.localSeq?.contains('m-')??false) {
			var localSeq = messageObject.localSeq;
			if (localSeq != null) {
				String serverId = localSeq.split("m-")[0];
				imMessage.serverId = serverId;
			}
		} else {
			imMessage.serverId = messageObject.localSeq;
		}
		imMessage.clientId = messageObject.localSeq;
		imMessage.fromUserId = messageObject.fromUserId;
		imMessage.toUserId = messageObject.toUserId;
		imMessage.groupCode = messageObject.groupCode;

		Set<MessageUserSendInfo>? messageUserSendInfoes = messageObject.userSendInfoSet;
		if (messageUserSendInfoes != null) {
			var messageSendSet = messageUserSendInfoes?.map((ui)=>_toImMessageSend(ui)).toSet();
			imMessage.messageSendSet = messageSendSet;
		}

		imMessage.refMessageIds = messageObject.refMessageIds;
		imMessage.status = ImMessageStatus.wrap(messageObject.status);
		bool isSender = true;
		// imMessage.fileStatus = ImMessageFileStatus.wrap(messageObject.fileStatus, isSender);
		imMessage.actionStatus = ImMessageActionStatus.wrap(messageObject.actionStatus, isSender);

		imMessage.version = 1;
		imMessage.messageType = ImMessageType.ofName(messageObject.messageType.name);
		imMessage.serverReceiveTime = messageObject.createDateTime;
		imMessage.content = messageObject.content;
		handleAttachFile(userId, imMessage, messageObject);
		await _buildFileStatus(imMessage);

		if (imSession != null) {
			imMessage.sessionId = imSession.id!;
			imMessage.imSession = imSession;
		} else {
			imMessage.status = ImMessageStatus.ref;
		}

		if (imMessage.isGroup) {
			final groupDao = ImGroupDao();
			final groupUserDao = ImGroupUserDao();
			var group = await ImCacheFactory.getInstance().getImGroup(imMessage.fromUserId!, imMessage.groupCode!);
		   // groupDao.getByCodeAndUserId(imMessage.groupCode!, userId);
			if (group?.id != null) {
				// var groupUser = await groupUserDao.getByGroupIdAndUserId(group!.id!, imMessage.fromUserId!);
				imMessage.fromGroupUser = group!.currentGroupUser; //groupUser;
			} else {
				_logger.i("check is null, ${group?.id??''}, ${imMessage.fromUserId??''}");
			}
		}
		return imMessage;
	}

	ImMessageSend _toImMessageSend(MessageUserSendInfo messageUserSendInfo) {
		var imMessageSend = new ImMessageSend(userId: messageUserSendInfo.userId,
				status: ImMessageStatus.wrap(messageUserSendInfo.status));
		var isSender = true;
		imMessageSend.fileStatus = ImMessageFileStatus.wrap(messageUserSendInfo.fileStatus, isSender);
		imMessageSend.actionStatus = ImMessageActionStatus.wrap(messageUserSendInfo.actionStatus, isSender);
		return imMessageSend;
	}

  Future<ImSession?> _getOrBuildImSession(String userId, MessageObject messageObject) async {
		final sessionDao = ImSessionDao();

		if (messageObject.isGroup()) {
			final groupDao = ImGroupDao();
			ImGroup? imGroup = await groupDao.getByCodeAndUserId(messageObject.groupCode!, userId);
			if (imGroup == null) { // 群不存在，就重新拉取群信息
				ImGroupService groupService = ImGroupService();
				imGroup = await groupService.pullGroup(userId, messageObject.groupCode!);
			}
			var imSession = await sessionDao.getByGroupCodeAndUserId(imGroup!.code, userId);
			return imSession != null ? Future.value(imSession) : null;
		} else {
			var imSession = await sessionDao.getByUserId(userId, messageObject.fromUserId, messageObject.toUserId??'');
			return imSession != null ? Future.value(imSession) : null;
		}
	}

	Future<ImSession> _buildImSession(String userId, MessageObject messageObject) async {
		final groupDao = ImGroupDao();

		String? toUserId = messageObject.toUserId;
		var imSession = null;
		if (messageObject.isP2p()) {
			if (userId == toUserId) {
				toUserId = messageObject.fromUserId;
			}
			if (toUserId != null) {
				var userDao = ImUserDao();
				ImUser? toUser = await userDao.getByUserId(toUserId);//await ImCacheFactory.getInstance().getImUser(toUserId);
				imSession = ImSession.buildWithUser(userId, toUser);
			}
		} else {
			ImGroup? imGroup = await groupDao.getByCodeAndUserId(messageObject.groupCode??'', userId);
			//await ImCacheFactory
			// .getInstance().getImGroup(userId, messageObject
			// .groupCode??'');
			imSession = ImSession.buildWithGroup(userId, imGroup!);
			imSession.name = imGroup.name;
		}

		imSession!.serverId = messageObject.sessionId;
		imSession!.status = ImSessionStatus.enable;
		String latelyMessage = MessageUtil.buildLastedMessage(ImMessageType.ofName(messageObject.messageType.name), messageObject.content);
		imSession.latelyMessage = latelyMessage;
		return imSession;
	}

	Future<void> initRefMessages(List<ImMessage> messages) async {
		final messageDao = ImMessageDao();

		for (var message in messages) {
			await messageDao.loadRefMessage(message);
		}
	}

	// 处理消息附件
	handleAttachFile(String userId, ImMessage imMessage, MessageObject message) {
		if(imMessage.messageType.isFile()) {
			FileItem? fileItem;
			MessageExt? messageExt;
			if (message.bean == null) {
				fileItem = parseString(message.content);
				if (fileItem == null) return;
				message.bean = fileItem;
				var fileName = fileItem.fileName;
				messageExt = MessageExt(remote: true, fileUrl: fileItem.fileUrl, md5: fileItem.md5, size: fileItem.size,
						originalName: fileItem.originalName, fileName: fileName, imageWidth: fileItem.imageWidth, imageHeight:
						fileItem.imageHeight);
				messageExt.time = fileItem.time;
				imMessage.content = fileItem.fileUrl;
			}
			imMessage.messageExt = messageExt;
			imMessage.init();
		}
	}

	deleteFile(String filePath) async {
		var file = File(filePath);
		await file.delete();
	}

	FileItem? parseString(String content) {
		try {
			return FileItem.fromJson(jsonDecode(content));
		} catch (error) {
			_logger.e('parse string to FileItem error, $content');
			// throw error;
		}
		return null;
	}

	Future<void> _buildFileStatus(ImMessage imMessage) async {
		if (imMessage.isAttachFile()) {
			var filePath = MessageUtil.getFilePath2(imMessage);
			if (FileUtil.fileExisted(filePath)) {
				ImMessageFileStatus fileStatusTmp = imMessage.isSender(imMessage.currentUserId) ? ImMessageFileStatus.downloaded :
					ImMessageFileStatus.downloaded;
				imMessage.fileStatus = fileStatusTmp;
			} else {
				var fileDao = ImFileDao();
				imMessage.initMessageExt();
				var imFile = await fileDao.getByMd5AndUserId(imMessage.messageExt?.md5??'', imMessage.currentUserId);
				if (imFile != null) {
					var messageExt = imMessage.messageExt;
					messageExt?.fileUrl = imFile.path;
					imMessage.init();
				} else {
					imMessage.fileStatus = ImMessageFileStatus.none;
				}
			}
		}
	}

	Future<void> handleImageSize(List<ImMessage> messages) async {
		var updateMessageExts = <ImMessage>[];
		for (var message in messages) {
			if (message.messageType != ImMessageType.Image) continue;
			if (message.messageExt == null) {
				message.initMessageExt();
			}
			if (message.messageExt!.imageHeight == null || message.messageExt!.imageWidth == null) {
				if (message.fileStatus == ImMessageFileStatus.downloaded || message.fileStatus == ImMessageFileStatus
						.uploaded || message.fileStatus == ImMessageFileStatus.uploading) {
					var path = MessageUtil.getFilePath2(message);
					var size = await ImageUtil.getImageSize(path);
					if (size != null) {
						message.messageExt?.imageWidth = size[0];
						message.messageExt?.imageHeight = size[1];
						message.init();
						updateMessageExts.add(message);
					}
				}
			}
		}
		var userId = ImSingleton.getInstance().session.userId;
		var messageDao = ImMessageDao();
		await messageDao.batchUpdateExts(userId!, updateMessageExts);
	}
}